package wcg;


import java.util.ArrayList;
import java.util.Iterator;
import java.io.IOException;

import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

/**
 * JTable上でCut/Copy/Pasteを行うためのリスナーです。
 * 
 */
public class TableEditActionListener extends AbstractAction implements ActionListener {
	
	/**
	 * シリアルバージョンUID
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * 本リスナーを追加するテーブル
	 */
	private JTable jTable;
	
	/**
	 * メニューアイテム（Cut, Copy, Paste）＝右クリック用
	 */
	private JMenuItem jmenuitem1, jmenuitem2, jmenuitem3;
	
	/**
	 * コマンド＝キーイベント用
	 */
	private String command;

	/**
	 * クリップボード
	 */
	private Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

	/**
	 * エラー発生時の終了コード
	 */
	private static int RETURN_ERROR = -1;

	/**
	 * 正常終了
	 */
	private static int RETURN_NORMAL = 0;

	/**
	 * コンストラクタ
	 *
	 * @param jTable コピー、ペーストを行うテーブル
	 * @param jmenuitem1 １つ目のメニューアイテム
	 * @param jmenuitem2 ２つ目のメニューアイテム
	 * @param jmenuitem3 ３つ目のメニューアイテム
	 */
	public TableEditActionListener(JTable jTable,
			JMenuItem jmenuitem1, JMenuItem jmenuitem2, JMenuItem jmenuitem3) {
		this.jTable = jTable;
		this.jmenuitem1 = jmenuitem1;
		this.jmenuitem2 = jmenuitem2;
		this.jmenuitem3 = jmenuitem3;
		this.command = "";
	}

	/**
	 * コンストラクタ
	 *
	 * @param jTable コピー、ペーストを行うテーブル
	 * @param command コマンド文字列
	 */
	public TableEditActionListener(JTable jTable, String command) {
		this.jTable = jTable;
		this.jmenuitem1 = null;
		this.jmenuitem2 = null;
		this.jmenuitem3 = null;
		this.command = command;
	}
	
	/**
	 * アクション（右クリックからメニュー選択、またはショートカットキー）発生時の処理
	 * 
	 */
	public void actionPerformed(ActionEvent e) {
		
		/** Cut **/
		if(e.getSource() == jmenuitem1 || command.equals("Cut")) {
			// カット処理
			processCopy(jTable, true);
		}
		/** Copy **/
		else if(e.getSource() == jmenuitem2 || command.equals("Copy")) {
			// コピー処理
			processCopy(jTable, false);
			return;
		}
		/** Paste **/
		else if(e.getSource() == jmenuitem3 || command.equals("Paste")) {
			// ペースト処理
			processPaste(jTable);
		}
		/** その他 **/
		else {
			// 未処理
		}
	}

	/**
	 * 切り取り／コピー処理を行います。
	 * 
	 * @param jTable 操作するテーブル
	 * @param delete コピー元データを削除する場合はtrue、そうでない場合はfalse
	 */
	private void processCopy(JTable jTable, boolean delete) {

		StringBuffer buffer = new StringBuffer();			// コピーするデータ用バッファ
		int numCols = jTable.getSelectedColumnCount();		// 選択されている列数
		int numRows = jTable.getSelectedRowCount();			// 選択されている行数
		int[] rowsSelected = jTable.getSelectedRows();		// 選択されている行のインデックス
		int[] colsSelected = jTable.getSelectedColumns();	// 選択されている列のインデックス
		
		// 連続していない複数のセルが選択されている場合はエラーを表示して終了
		if(RETURN_ERROR == checkSelectError(jTable)) {
			return;
		}
		
		/* セルからデータ取得 */
		for(int i = 0; i < numRows; i++) {
			for(int j = 0; j < numCols; j++) {
				// １セルずつバッファにデータをコピー
				buffer.append(jTable.getValueAt(rowsSelected[i], colsSelected[j]));
				/** 削除指定あり **/
				if(delete) {
					// コピーした箇所に""を設定することにより、データ消去
					jTable.setValueAt("", rowsSelected[i], colsSelected[j]);
				}
				// １データ間毎にタブを挿入
				if(j < numCols-1) buffer.append("\t");
			}
			// 行が変わるとLFを挿入
			buffer.append("\n");
		}
		
		// 文字列としてバッファのデータをクリップボードに転送する
		StringSelection ss = new StringSelection(buffer.toString());
		clipboard.setContents(ss, ss);
		
		// JTableを再描画
		jTable.repaint();
	}
	
	/**
	 * ペースト処理を行います。<p>
	 * ※行、セル分割時にStringTokenizerを使用すると未入力のセルが飛ばされてしまうため、危険。
	 * 
	 * @param jTable 操作するテーブル
	 */
	private void processPaste(JTable jTable) {
		
		String 	rowStr;										// １行全体を連結した文字列
		if(jTable.getSelectedRows().length == 0 ||jTable.getSelectedColumns().length==0){
			return ;
		}
		int startRow = jTable.getSelectedRows()[0];			// 選択された先頭行のインデックス
		int startCol = jTable.getSelectedColumns()[0];		// 選択された先頭列のインデックス
		int endRow = jTable.getSelectedRows()[jTable.getSelectedRows().length-1];
															// 選択された最終行のインデックス
		int endCol = jTable.getSelectedColumns()[jTable.getSelectedColumns().length-1];
															// 選択された先頭列のインデックス
		
		// 連続していない複数のセルが選択されている場合はエラーを表示して終了
		if(RETURN_ERROR == checkSelectError(jTable)) {
			return;
		}
		
		// クリップボードの内容を転送可能なオブジェクトとして取得する
		Transferable transferable = clipboard.getContents(jTable);
		
		/** クリップボードにデータがあれば **/
		if(transferable != null) {
			try{
				// クリップボードのデータを文字列として取得
				String str = (String)transferable.getTransferData(DataFlavor.stringFlavor);
				
				/*----------*/
				/* １行分割 */
				/*----------*/
				// LFの位置を取得する
				ArrayList<Integer> listLf = new ArrayList<Integer>();
				for(int i = 0; i < str.length(); i++) {
					if(str.charAt(i) == '\n')	listLf.add(i);
				}
				// １行単位にする
				ArrayList<String> listLine = new ArrayList<String>();
				for(int i = 0, pos = 0; i < listLf.size(); i++) {
					listLine.add(str.substring(pos, listLf.get(i)));
					pos = listLf.get(i) + 1;
				}

				// 選択したセルよりも複写先セルの数が多い場合は、同じものを繰り返し貼り付ける
				for(int i = startRow; (i == startRow) || ((i + listLine.size() - 1) <= endRow);
					i += listLine.size()) {
					
					Iterator<String> iteratorLine = listLine.iterator();
					for(int j = 0; iteratorLine.hasNext(); j++) {
						// １行の文字列
						rowStr = iteratorLine.next();
						
						/*----------*/
						/* セル分割 */
						/*----------*/
						// タブの位置を取得する
						ArrayList<Integer> listTab = new ArrayList<Integer>();
						for(int k = 0; k < rowStr.length(); k++) {
							if(rowStr.charAt(k) == '\t')	listTab.add(k);
						}
						// セル単位にする
						ArrayList<String> listCell = new ArrayList<String>();
						int pos = 0;
						for(int k = 0; k < listTab.size(); k++) {
							listCell.add(rowStr.substring(pos, listTab.get(k)));
							pos = listTab.get(k) + 1;
						}
						// 最後のセル
						if(pos <= (rowStr.length()-1)) {
							listCell.add(rowStr.substring(pos, rowStr.length()));
						}
						else {
							// 最後のセルが何も無い場合
							listCell.add("");
						}
						
						// 選択したセルよりも複写先セルの数が多い場合は、同じものを繰り返し貼り付ける
						for(int x = startCol; (x == startCol) || ((x + listCell.size() - 1) <= endCol);
							x += listCell.size()) {
							
							Iterator<String> iteratorCell = listCell.iterator();
							for(int y = 0; iteratorCell.hasNext(); y++) {
								// １セル分のデータを取得
								String value = iteratorCell.next();
								// 貼り付け対象セルがテーブルからはみ出していなければ
								if(i + j < jTable.getRowCount() &&
									x + y < jTable.getColumnCount()) {
									// １セル分のデータを貼り付け
									jTable.setValueAt(value, i + j, x + y);
								}
							}
						}
					}
				}
			}
			catch(IOException e1){
				e1.printStackTrace();
			}
			catch(UnsupportedFlavorException e2){
				e2.printStackTrace();
			}
			catch(NullPointerException e3){
				e3.printStackTrace();
			}
			catch(Throwable t){
				t.printStackTrace();
			}
			
			// JTableを再描画
			jTable.repaint();
		}
	}
	
	/**
	 * 選択箇所が複数の連続していない範囲となっていないかどうか調べます。<br>
	 * 複数の連続していない範囲を選択している場合は、エラーメッセージを表示します。
	 * 
	 * @param jTable 対象のテーブル
	 * @return 正常の場合はRETURN_NORMAL、エラーの場合はRETURN_ERROR
	 */
	private int checkSelectError(JTable jTable) {
		
		int numCols = jTable.getSelectedColumnCount();		// 選択されている列数
		int numRows = jTable.getSelectedRowCount();			// 選択されている行数
		int[] rowsSelected = jTable.getSelectedRows();		// 選択されている行のインデックス
		int[] colsSelected = jTable.getSelectedColumns();	// 選択されている列のインデックス
			
		if(rowsSelected.length==0 || colsSelected.length==0){
			return RETURN_ERROR;
		}
		/** 連続していない複数のセルが選択されている場合 **/
				// 列のインデックスが連続していない
		if(!((numRows-1 == rowsSelected[rowsSelected.length-1] - rowsSelected[0]) &&
				// 選択されている列の数が列のインデックス数と一致しない
				(numRows == rowsSelected.length) &&
				// 行のインデックスが連続していない
				(numCols-1 == colsSelected[colsSelected.length-1] - colsSelected[0]) &&
				// 選択されている行の数が行のインデックス数と一致しない
				(numCols == colsSelected.length))) {
			
			// エラーメッセージを表示する
			JOptionPane.showMessageDialog(null, 
					"連続していない複数の選択範囲に対してそのコマンドを使用することは出来ません。",
					"エラー",
					JOptionPane.ERROR_MESSAGE);
			return RETURN_ERROR;
		}
		return RETURN_NORMAL;
	}
}

